<?php
//
// +------------------------------------------------------------------------+
// | PHP Version 5                                                          |
// +------------------------------------------------------------------------+
// | Copyright (c) All rights reserved.                                     |
// +------------------------------------------------------------------------+
// | This source file is subject to version 3.00 of the PHP License,        |
// | that is available at http://www.php.net/license/3_0.txt.               |
// | If you did not receive a copy of the PHP license and are unable to     |
// | obtain it through the world-wide-web, please send a note to            |
// | license@php.net so we can mail you a copy immediately.                 |
// +------------------------------------------------------------------------+
// | Author:                                                                |
// +------------------------------------------------------------------------+
//
// $Id: Select.php 48 2008-06-25 00:32:17Z garfield0601 $
//


/**
* @author       vv
*/
require_once "SmartPHP/Db/Select/Exception.php";
/**
 * SmartPHP_Db_Select 
 * 
 * @package 
 * @version $id$
 * @copyright 1997-2008 Garfield
 * @author Garfield<garfield0601@gmail.com> 
 * @license http://framework.zend.com/license/new-bsd     New BSD License
 */
class SmartPHP_Db_Select
{
    
	const FROM 		= "from";
	const JOIN 		= "join";
	const FIELDS 	= "fields";
	const WHERE 	= "where";
	const ORWHERE 	= "orwhere";
	const ORDER 	= "order";
	const ASC 		= 'asc';
	const DESC 		= 'desc';
	const GROUP		= "group";
	const LIMIT 	= "limit"; 
	const DISTINCT 	= 'distinct'; 
    const INNER_JOIN     = ' inner join ';
    const LEFT_JOIN      = ' left join ';
    const RIGHT_JOIN     = ' right join ';
    const FULL_JOIN      = ' full join ';
    const CROSS_JOIN     = ' cross join ';
    const NATURAL_JOIN   = ' natural join ';

    const SQL_WILDCARD   = ' * ';
	const SQL_SPACE		 = " ";
    const SQL_CONNECTOR  = '.';
    const SQL_SEPARATOR  = ',';
    const SQL_SELECT     = ' SELECT ';
    const SQL_FROM       = ' FROM ';
    const SQL_WHERE      = ' WHERE ';
    const SQL_DISTINCT   = ' DISTINCT ';
    const SQL_LIMIT		 = ' LIMIT ';
    const SQL_GROUP_BY   = ' GROUP BY ';
    const SQL_ORDER_BY   = ' ORDER BY ';
    const SQL_HAVING     = ' HAVING ';
    const SQL_FOR_UPDATE = ' FOR UPDATE ';
    const SQL_AND        = ' AND ';
    const SQL_AS         = ' AS ';
    const SQL_OR         = ' OR ';
    const SQL_ON         = ' ON ';
    const SQL_ASC        = ' ASC ';
    const SQL_DESC       = ' DESC ';
    CONST SQL_INNER_JOIN     = ' INNER JOIN ';
    CONST SQL_LEFT_JOIN      = ' LEFT JOIN ';
    CONST SQL_RIGHT_JOIN     = ' RIGHT JOIN ';
    CONST SQL_FULL_JOIN      = ' FULL JOIN ';
    CONST SQL_CROSS_JOIN     = ' CROSS JOIN ';
    CONST SQL_NATURAL_JOIN   = ' NATURAL JOIN ';

    /**
     * The initial values for the $_parts array.
     *
     * @var array
     */
    protected static $_joinTypes = array(
        self::INNER_JOIN,
        self::LEFT_JOIN,
        self::RIGHT_JOIN,
        self::FULL_JOIN,
        self::CROSS_JOIN,
        self::NATURAL_JOIN,
    );

    /**
    * @var      array
    */
    private $_parts = array();

	public function __construct()
	{
		 $this->_parts[self::FIELDS][] = self::SQL_WILDCARD;
	}
    
    /**
     * from 
     * 
     * @param string|array $tableName 
     * @access public
     * @return void
	 *
     */
    public function from( $tableName )
    {
		if( is_array($tableName) )
		{
			 reset($tableName);
			 $tableName = current($tableName);
		}
		elseif( is_string( $tableName ) )
		{
			$tableName = (string)$tableName;
		}
		else 
		{
			 throw new SmartPHP_Db_Select_Exception("Invalid param!Should be a string or an array!");
		}
		$this->_parts[self::FROM] = self::SQL_FROM . $tableName ;
		return $this;
    }
    
    /**
     * where 
	 *
     * @param mixed $cond 
     * @access public
     * @return object
     */
    public function where( $cond )
    {
		if( is_array($cond) )
		{
			 $this->_parts[self::WHERE][] = implode( self::SQL_AND , $cond ) ;
		}
		elseif( is_string($cond) )
		{
			 $this->_parts[self::WHERE][] = $cond;
		}
		else
		{
			 throw new SmartPHP_Db_Select_Exception("Bad Param for where()");
		}
		return $this;
    }
    
    /**
     * orWhere 
     * 
     * @param mixed $cond 
     * @access public
     * @return void
     */
    public function orWhere( $cond )
    {
		if( is_array($cond) )
		{
			 $this->_parts[self::ORWHERE][] = implode( self::SQL_OR , $cond );
		}
		elseif( is_string($cond) )
		{
			 $this->_parts[self::ORWHERE][] = $cond ;
		}
		else
		{
			 throw new SmartPHP_Db_Select_Exception("Bad Param for where()");
		}
		return $this;
    }
    
    /**
    * @return   object
    */
    /**
     * fields() 
     * 
	 * @param mixed $field 
	 * @$field是数组的情况
	 * 		$field是索引数组，直接转变为字符串
	 * 		$field是关联数组，键名是表名，
	 * 		值又分为两种情况。
	 * 			值如果是索引数组，则直接转变为查询字段字符串。
	 * 			值如果是关联数据，则键名是原字段名，值为字段别名。
	 *
	 * $field是字符串，
	 * 		直接返回使用。
     * @access public
     * @return void
     */
    public function fields($fields = "*")
    {
		$this->_parts[self::FIELDS] = array();
		$index = 0;
		$fieldsStr = "";
		if( is_array($fields) )
		{
			foreach( $fields as $key => $val )
			{
				$fieldsStr .= $index === 0 ? "" : self::SQL_SEPARATOR ;
				if( is_int($key) )
				{
					 $fieldsStr .= (string)$val;
				}
				else
				{
					 $fieldsStr .= (string)$key . self::SQL_AS . (string)$val ;
				}
				$index = 1;
			}
		}
		elseif( is_string($fields) )
		{
			 $fieldsStr = $fields;
		}
		else
		{
			throw SmartPHP_Db_Select_Exception("Invalid param for fields()");
		}
		$this->_parts[self::FIELDS][] = $fieldsStr;
		return $this;
    }
    
    /**
     * _join 
     * 
     * @param mixed $joinType 
     * @param mixed $joinTableName 
     * @param string $joinCond 
     * @param mixed $joinTableFields 
     * @access private
     * @return string
     */
    private function _join( $joinType , $joinTableName , $joinCond = null, $joinTableFields = null )
    {
		$joinStr = "";
		if (!in_array($joinType, self::$_joinTypes)) 
		{
            /**
             * @see SmartPHP_Db_Select_Exception
             */
            require_once 'SmartPHP/Db/Select/Exception.php';
            throw new SmartPHP_Db_Select_Exception("Invalid join type '$type'");
        }
		if(is_array($joinTableName))
		{
			foreach($joinTableName as $key => $val)
			{
				if( !is_int($key) && is_string($key) )
				{
					$joinStr .= strtoupper($joinType) . $val . self::SQL_AS . $key ;
					$joinStr .= isset($joinCond) ? self::SQL_ON . $joinCond : "";
				}
				else 
				{
					$joinStr .= strtoupper($joinType) . $val ;
					$joinStr .= isset($joinCond) ? self::SQL_ON . $joinCond : "";
				}
				break;
			}
		}
		elseif( is_string($joinTableName) )
		{
			$joinStr .= strtoupper($joinType) . $joinTableName;
			$joinStr .= isset($joinCond) ? self::SQL_ON . $joinCond : "";
		}
		else 
		{
			throw SmartPHP_Db_Select_Exception("Invalid type for the 2nd parameter.Should be string or array");
		}
		if( isset($joinTableFields) && !empty($joinTableFields) )
		{
			$this->fields( $joinTableFields );
		}
		return $joinStr;
    }
    
    /**
     * leftJoin 
     * 
     * @param mixed $joinTableName 
     * @param mixed $joinCond 
     * @param mixed $joinTableFields 
     * @access public
     * @return object
     */
    public function leftJoin( $joinTableName , $joinCond = null, $joinTableFields = null )
    {
		$this->_parts[self::JOIN][] = $this->_join(self::LEFT_JOIN , $joinTableName , $joinCond, $joinTableFields );
		return $this;
    }
    
    /**
     * rightJoin 
     * 
     * @access public
     * @return object
     */
    public function rightJoin( $joinTableName , $joinCond = null, $joinTableFields = null )
    {
		$this->_parts[self::JOIN][] = $this->_join(self::RIGHT_JOIN , $joinTableName , $joinCond, $joinTableFields );
		return $this;
    }
    
    /**
     * innerJoin 
     * 
     * @param mixed $joinTableName 
     * @param string $joinCond 
     * @param mixed $joinTableFields 
     * @access public
     * @return void
     */
    public function innerJoin( $joinTableName , $joinCond = null, $joinTableFields = null )
    {
		$this->_parts[self::JOIN][] = $this->_join(self::INNER_JOIN , $joinTableName , $joinCond, $joinTableFields );
		return $this;
    }
    
    /**
     * join 
     * 
     * @param mixed $joinTableName 
     * @param string $joinCond 
     * @param mixed $joinTableFields 
     * @access public
     * @return void
     */
    public function join( $joinTableName , $joinCond = null, $joinTableFields = null )
    {
		$this->_parts[self::JOIN][] = $this->_join(self::INNER_JOIN , $joinTableName , $joinCond, $joinTableFields );
		return $this;
    }
    /**
     * naturalJoin 
     * 
     * @param mixed $joinTableName 
     * @access public
     * @return void
     */
    public function naturalJoin( $joinTableName )
    {
		$this->_parts[self::JOIN][] = $this->_join(self::NATURAL_JOIN , $joinTableName );
		return $this;
    }
    
    /**
     * order 
     * 
     * @access public
     * @return void
     */
    public function order( $order )
    {
		$index = 0;
		$orderStr = "";
		$this->_parts[self::ORDER] = "";
		if( is_array($order) )
		{
			foreach($order as $key=>$val)
			{
				 $orderStr .= $index == 0 ? "" : self::SQL_SEPARATOR ;
				 if( is_int($key) )
				 {
					  $orderStr .= $val;
				 }
				 else
				 {
					 $val = strtolower($val);
					 if( ($val != self::ASC) && ($val != self::DESC) )
					 {
						  $val = self::SQL_ASC;
					 }
					 $val = $val == self::ASC ? self::SQL_ASC : self::SQL_DESC;
					 $orderStr .= $key . $val;
				 }
				 $index = 1;
			}
		}
		elseif( is_string($order) )
		{
			 $orderStr = $order;
		}
		else
		{
			return $this;
		}
		$this->_parts[self::ORDER][] = $orderStr;
		return $this;
    }
    
    /**
    * @return   object
    */
    /**
     * group 
     * 
     * @access public
     * @return void
     */
    public function group( $param )
    {
		if( is_array( $param ) )
		{
			 $this->_parts[self::GROUP] = implode( self::SQL_SEPARATOR , $param );
		}
		elseif( is_string( $param ) )
		{
			 $this->_parts[self::GROUP] = $param ;
		}
		else 
		{
			 throw new SmartPHP_Db_Select_Exception("Invalid parameter for group()");
		}
		return $this;
    }
    
    /*
    * @return   object
    */
    public function limit($offset , $num)
    {
		$this->_parts[self::LIMIT] = self::SQL_LIMIT . $offset . self::SQL_SEPARATOR . $num;
		return $this;
    }

	/**
	 * _renderJoin 
	 * 
	 * @access private
	 * @return void
	 */
	private function _renderJoin($sql)
	{
		if( $this->_parts[self::JOIN] )
		{
			$sql .=	implode( self::SQL_SPACE , $this->_parts[self::JOIN] );
		}
		return $sql;
	}
	/**
	 * _renderWhere 
	 * 
	 * @access private
	 * @return void
	 */
	private function _renderWhere($sql)
	{
		if( $this->_parts[self::WHERE] || $this->_parts[self::ORWHERE] )
		{
			$sql .= self::SQL_WHERE ;
			if( !empty($this->_parts[self::WHERE]) )
			{
				$sql .= implode(self::SQL_AND , $this->_parts[self::WHERE] ) ;
			}
			if( !empty($this->_parts[self::ORWHERE]) )
			{
				$sql .= !empty( $this->_parts[self::WHERE] ) ? self::SQL_OR : "";
				$sql .= implode(self::SQL_OR , $this->_parts[self::ORWHERE] ) ;
			}
		}
		return $sql;
	}
	/**
	 * _renderGroup 
	 * 
	 * @access private
	 * @return void
	 */
	private function _renderGroup($sql)
	{
		if( $this->_parts[self::GROUP] )
		{
			 $sql .= self::SQL_GROUP_BY . $this->_parts[self::GROUP];
		}
		return $sql;
	}
	/**
	 * _renderOrder 
	 * 
	 * @access private
	 * @return void
	 */
	private function _renderOrder($sql)
	{
		if( $this->_parts[self::ORDER] ) 
		{
			$sql .= self::SQL_ORDER_BY . implode( self::SQL_SEPARATOR , $this->_parts[self::ORDER] );
		}
		return $sql;
	}
	/**
	 * _renderLimit 
	 * 
	 * @access private
	 * @return void
	 */
	private function _renderLimit($sql)
	{
		if( $this->_parts[self::LIMIT] )
		{
			 $sql .= $this->_parts[self::LIMIT]; 
		}
		return $sql; 
	}
	/**
	 * _renderFields 
	 * 
	 * @param mixed $sql 
	 * @access private
	 * @return void
	 */
	private function _renderFields($sql)
	{
		if(empty($this->_parts[self::FIELDS])) return $sql . self::SQL_WILDCARD;
		return $sql . implode( self::SQL_SEPARATOR , $this->_parts[self::FIELDS] );
	}
	private function _renderFrom( $sql )
	{
		 return $sql . $this->_parts[self::FROM];
	}
    /**
     * __toString 
     * 
     * @access private
     * @return string
     */
    public function __toString()
    {
		$sql = self::SQL_SELECT ;
		$sql = $this->_renderFields($sql);
		$sql = $this->_renderFrom($sql);
		$sql = $this->_renderJoin($sql);
		$sql = $this->_renderWhere($sql);
		$sql = $this->_renderOrder($sql);
		$sql = $this->_renderGroup($sql);
		$sql = $this->_renderLimit($sql);
		return $sql;
    }
}

?>
